﻿using System;
using System.Text;

namespace Server.Core.Serialization.BooMessaging
{
    public class BooMessageWriter
    {
        private const int InitialBufferSize = 256;
        private const int IntSize = 4;
        private const int MaxPackageLevel = 6;
        private static readonly Encoding DefaultEncoding = Encoding.UTF8;

        private byte[] _buffer;
        private int _bufNext; // the next index of _buffer
        private int _bufLen; // equals _buffer.Length
        private int[] _pkgStack;
        private int _pkgPos; // current position of _packageLevel

        public BooMessageWriter()
        {
            _buffer = new byte[InitialBufferSize];
            _pkgStack = new int[MaxPackageLevel];
            _pkgPos = -1;
        }

        public byte[] GetBuffer()
        {
            if (_pkgPos >=0)
                throw new BooFormatException("There are still packages not ended.");

            return _buffer;
        }

        public void WriteField(string value)
        {
            if (value == null) // empty field
                return;

            var valueBytes = DefaultEncoding.GetBytes(value);
            var valueLen = valueBytes.Length;
            var totalLen = _bufNext + IntSize + valueLen;
            var sizeBytes = BitConverter.GetBytes(valueLen);

            if (totalLen > _bufLen)
                EnlargeBuffer(totalLen);

            Push(sizeBytes);
            Push(valueBytes);
        }

        public void StartPackage()
        {
            if (_pkgPos >= MaxPackageLevel)
                throw new InvalidOperationException("Max package level received.");

            if (_bufNext + IntSize > _bufLen)
                EnlargeBuffer(_bufNext + IntSize);

            _pkgStack[++_pkgPos] = 0; // stack push
            _bufNext += IntSize;
        }

        public void EndPackage()
        {
            if (_pkgPos <= 0)
                throw new InvalidOperationException("No packaged started.");

            var pkgLen = _pkgStack[--_pkgPos]; // stack pop
            var pkgLenBytes = BitConverter.GetBytes(pkgLen);
            var pos = _bufNext - pkgLen - IntSize;

            Buffer.BlockCopy(pkgLenBytes, 0, _buffer, pos, pkgLenBytes.Length);
        }

        private void Push(byte[] bytes)
        {
            var len = bytes.Length;
            Buffer.BlockCopy(bytes, 0, _buffer, _bufNext, len);
            _bufNext += len;

            if (_pkgPos >= 0)
            {
                _pkgStack[_pkgPos] += len;
            }
        }

        private void EnlargeBuffer(int dataLen)
        {
            var newLen = (dataLen * 2) + 1;
            var newBuffer = new byte[newLen];

            Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _bufLen);

            _buffer = newBuffer;
            _bufLen = newLen;
        }
    }
}
